﻿// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using Castle.Core;
using Castle.Core.Resource;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using CIP4.Common;
using CIP4.RulesEngine;
using CIP4.Simple;
using Magnum;
using MassTransit;
using MassTransit.Transports.Msmq;
using MassTransit.WindsorIntegration;

namespace CIP4.DataProvider.MassTransit
{
    public class ClientHandler : 
        Consumes<ResponseMessage>.For<Guid>
    {
        private readonly IServiceBus _bus;
        private UnsubscribeAction _unsubscribeToken;

        private readonly string _busId;

        private readonly int _timeOut;

        private Guid _correlationId;

        public ClientHandler(LoggerDelegate logger, string busId, int timeOut)
        {
            try
            {
                _timeOut = timeOut;
                _busId = busId;
                _bus = BootstrapContainer().Resolve<IServiceBus>();
                if (_bus == null)
                    throw new ArgumentException(string.Format("Unable to allocate bus for {0}", busId)); 
            }
            catch (Exception ex)
            {
                Globals.LoggerWrapper(MajorEnum.Error, MinorEnum.ErrorResolvingBus,
                    "Error connecting to Mass Transit bus: {0}", new object[] {ex}, logger);
            }
        }

        private IWindsorContainer BootstrapContainer()
        {
            MsmqEndpointConfigurator.Defaults(x => { x.CreateMissingQueues = true; });

            StaticContentResource scr = new StaticContentResource(WindsorMaker.GetWindsorXml(_busId, "client"));

            XmlInterpreter xi = new XmlInterpreter(scr);

            IWindsorContainer container = new DefaultMassTransitContainer(xi);
            container.AddComponent<ClientHandler>();
            //container.AddComponentLifeStyle<ClientHandler>(LifestyleType.Transient);
            return container;
        }

        private List<object> _results;

        private bool _done;

        private ResponseMessage _response;

        private bool Finished()
        {
            int sleepCount = 0;
            while (!_done && (++sleepCount < (_timeOut / 10)))
                Thread.Sleep(10);
            return sleepCount < (_timeOut / 10);
        }

        private static string ExpressionString(IBooleanValue selectionExpression)
        {
            if (selectionExpression == null)
                return null;
            StringWriter swrt = new StringWriter();
            XmlTextWriter xwrt = new XmlTextWriter(swrt);
            selectionExpression.XmlFor(xwrt, true);
            return swrt.ToString();

        }

        private void ProcessResponse(LoggerDelegate logger)
        {
            if (_response == null)
                return;

            logger(_response.Status, _response.MinorStatus, _response.Message, new object[]{});
            if (_response.Paras != null && _response.Paras.Count > 0 && _response.Paras[0] is ResponseMessage)
                foreach (ResponseMessage resp in _response.Paras)
                {
                    logger(resp.Status, resp.MinorStatus, resp.Message, resp.Paras.ToArray());
                }
        }


#region Consumes functions

        public void Consume(ResponseMessage message)
        {
            _response = message;
            if (message.Values != null)
                _results = message.Values;
            _done = true;
        }

        public Guid CorrelationId
        {
            get { return _correlationId; }
        }

#endregion

#region IDataProvider functions

        public IEnumerable<object> GetItems(CollectionType collection, IBooleanValue selectionExpression, CursorType cursor)
        {
            _correlationId = CombGuid.Generate();

            RequestMessage request =
                new RequestMessage
                {
                    Operation = RequestMessage.OperationEnum.GetItems,
                    CorrelationTag = _busId,
                    CorrelationId = _correlationId,
                    ExpectedType = collection.CollectionCompoundType.Name,
                    SelectionExpression = ExpressionString(selectionExpression)
                };

            if (_unsubscribeToken != null)
                _unsubscribeToken();
            _unsubscribeToken = _bus.Subscribe(this);

            _done = false;

            _results = new List<object>();

            _bus.Publish(request, x => x.SetResponseAddress(_bus.Endpoint.Uri));

            if (Finished())
                ProcessResponse(cursor.Logger);
            else
                Globals.LoggerWrapper(MajorEnum.Warning, MinorEnum.TimedOut, "The request to the bus timed out", new object[] {}, cursor.Logger);

            return _results;
        }

        public void SaveItems(IEnumerable<object> items, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            _correlationId = CombGuid.Generate();

            RequestMessage request =
                new RequestMessage
                {
                    Operation = RequestMessage.OperationEnum.SaveItems,
                    CorrelationTag = _busId,
                    CorrelationId = _correlationId,
                    ExpectedType = collection.CollectionCompoundType.Name,
                    Values = items.ToList()
                };

            if (_unsubscribeToken != null)
                _unsubscribeToken();
            _unsubscribeToken = _bus.Subscribe(this);

            _done = false;

            _bus.Publish(request, x => x.SetResponseAddress(_bus.Endpoint.Uri));

            ISerializerType serializer = getSerializer(SerializerKindEnum.CIP4Xml);

            if (Finished())
                ProcessResponse(serializer.Logger);
            else
                serializer.Logger(MajorEnum.Warning, MinorEnum.Generic, "Timed out saving {0} items", new object[] { request.Values.Count });
        }

        public void PurgeItems(IEnumerable<ExternalIDType> externalIDs, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            _correlationId = CombGuid.Generate();

            List<string> externalIDTexts = new List<string>();

            foreach (ExternalIDType id in externalIDs)
                externalIDTexts.Add(id.ToString());

            RequestMessage request =
                new RequestMessage
                {
                    Operation = RequestMessage.OperationEnum.PurgeItems,
                    CorrelationTag = _busId,
                    CorrelationId = _correlationId,
                    ExpectedType = collection.CollectionCompoundType.Name,
                    Values = externalIDTexts.Cast<object>().ToList()
                };

            if (_unsubscribeToken != null)
                _unsubscribeToken();
            _unsubscribeToken = _bus.Subscribe(this);

            _done = false;

            _bus.Publish(request, x => x.SetResponseAddress(_bus.Endpoint.Uri));

            ISerializerType serializer = getSerializer(SerializerKindEnum.CIP4Xml);

            if (Finished())
                ProcessResponse(serializer.Logger);
            else
                serializer.Logger(MajorEnum.Warning, MinorEnum.Generic, "Timed out deleting {0} items", new object[] { request.Values.Count });
        }

        public void DeleteCollection(CollectionType collection, LoggerDelegate logger)
        {
            _correlationId = CombGuid.Generate();

            RequestMessage request =
                new RequestMessage
                {
                    Operation = RequestMessage.OperationEnum.DeleteCollection,
                    CorrelationTag = _busId,
                    CorrelationId = _correlationId,
                    ExpectedType = collection.CollectionCompoundType.Name,
                };

            if (_unsubscribeToken != null)
                _unsubscribeToken();
            _unsubscribeToken = _bus.Subscribe(this);

            _done = false;

            _bus.Publish(request, x => x.SetResponseAddress(_bus.Endpoint.Uri));

            if (Finished())
                ProcessResponse(logger);
            else
                logger(MajorEnum.Warning, MinorEnum.Generic, "Timed out deleting collection {0}", new object[] { collection.Name });
        }

#endregion
    }
}
